package handle

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"MPCBot/global"
	"MPCBot/model"
	"MPCBot/utils"
	"MPCBot/utils/ether"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/crypto"
	hdwalletEth "github.com/miguelmota/go-ethereum-hdwallet"
	"github.com/tyler-smith/go-bip39"
)

var (
	robotAccountList  []*model.Account
	accountChannel    ChannelI[int]
	botSignChannel    ChannelI[int]
	hYOrderChannel    chan string
	hyOrderKeys       []string
	globalHyOrderList []model.HybridgeOrder
	hermitClient      HermitClient
)

type ChannelI[V int] chan V

// system Initialize
func Init() {
	defer func() {
		if err := recover(); err != nil {
			global.Log.Error("service Initialization fail：%v", err)
		}
	}()

	// Generate robot accountlist
	if MakeRobotAccountList() {
		global.Log.Info("====> service Initializing ......")
		switch global.Conf.RunParams.ServiceType {
		case model.BotSign:
			BatchSignOrder()
		case model.ExecCrossChainTask:
			hermitClient.Init()
			BatchExecHyOrder()
		case model.ExportAccountPrivate:
			mnemonic, err := ioutil.ReadFile(global.Conf.RunParams.ExportAccountPath)
			if err != nil {
				panic(err)
			}
			hermitClient.ExportAccountPrivate(string(mnemonic))
		}
		global.Log.Info("====> service Initialization complete")
	}
}

// Generate robot accountlist
func MakeRobotAccountList() bool {
	global.Log.Info("====> Generate robot accountlist begin ......")
	global.Log.Info("====> please input three mnemonic words")
	var inputWords string
	for i := 0; i < 3; i++ {
		words, _ := utils.GetPasswdPrompt("Please enter the "+strconv.Itoa(i)+" word :", false, os.Stdin, os.Stdout)
		inputWords += " " + strings.ToLower(string(words))
	}
	mnemonic, err := ioutil.ReadFile(global.Conf.RunParams.MnemonicPath)
	if err != nil {
		panic(err)
	}
	mnemonicStr := string(mnemonic) + inputWords
	seed := bip39.NewSeed(mnemonicStr, "") // config pasword for mnemonic
	wallet, err := hdwalletEth.NewFromSeed(seed)
	if err != nil {
		log.Fatal(err)
		return false
	}

	if global.Conf.RunParams.RobotAccountsCount < 1 {
		fmt.Println(model.Red, "RobotAccountsCount is config error", model.Reset)
		return false
	}

	for i := 0; i < global.Conf.RunParams.RobotAccountsCount; i++ {
		paths := "m/44'/60'/0'/0/" + strconv.Itoa(i)
		path := hdwalletEth.MustParseDerivationPath(paths)
		account, err := wallet.Derive(path, false)
		if err != nil {
			log.Fatal(err)
			return false
		}

		address := account.Address.Hex()
		privateKey, _ := wallet.PrivateKeyHex(account)

		var accounttemp model.Account
		accounttemp.Addr = address
		accounttemp.PriKey = privateKey
		robotAccountList = append(robotAccountList, &accounttemp)
		global.Log.Info("====> robot address[%v]:%v", i, accounttemp.Addr)
	}

	return true
}

// Get need sign order
func GetSignOrder() {
	global.Log.Info("====> get sign order from api init channel ......")
	if getHandleHyOrderList() {
		if len(globalHyOrderList) <= 0 {
			fmt.Println(model.Yellow, " No any order need handel After", global.Conf.RunParams.GetOrderInterval, " seconds try again ......", model.Reset)
			return
		}
		botSignChannel = make(ChannelI[int], len(globalHyOrderList))
		for index, _ := range globalHyOrderList {
			botSignChannel <- index
			global.Log.Info("====> botSignChannel push %v_%v", index, globalHyOrderList[index].ID)
		}
	}
}

// Get need exec order
func GetExecOrder() {
	global.Log.Info("====> get exec order from api init channel ......")
	if getRedisHyOrderTask() {
		if len(hyOrderKeys) <= 0 {
			fmt.Println(model.Yellow, " No any order need handel After", global.Conf.RunParams.GetOrderInterval, " seconds try again ......", model.Reset)
			return
		}
		hYOrderChannel = make(chan string, len(hyOrderKeys))
		for _, hyOrderKey := range hyOrderKeys {
			hYOrderChannel <- hyOrderKey
			global.Log.Info("====> hYOrderChannel push payOrderId_[%v]", hyOrderKey)
		}
	}
}

// batch sign order
func BatchSignOrder() {
	GetSignOrder()
	accountChannel = make(ChannelI[int], len(robotAccountList))
	for accountIndex, _ := range robotAccountList {
		accountChannel <- accountIndex
		global.Log.Info("====> accountChannel push %v_%v", accountIndex, robotAccountList[accountIndex].Addr)
	}
	for {
		select {
		case orderIndex := <-botSignChannel:
			global.Log.Info("====> botSignChannel pop index_[%v]_orderid[%v]", orderIndex, globalHyOrderList[orderIndex].ID)
			if accountIndex, ok := <-accountChannel; ok {
				global.Log.Info("====> accountChannel pop index_[%v]_address[%v]", accountIndex, robotAccountList[accountIndex].Addr)
				go func() {
					defer func() {
						if err := recover(); err != nil {
							global.Log.Error("====>  botSignChannel sign order_[%v] fail：%v", globalHyOrderList[orderIndex].ID, err)
						}
						time.Sleep(100 * time.Millisecond)
						accountChannel <- accountIndex
						global.Log.Info("====> accountChannel push index_[%v]_address[%v]", accountIndex, robotAccountList[accountIndex].Addr)
					}()
					// make bot sign
					MakeBotSign(robotAccountList[accountIndex], &globalHyOrderList[orderIndex])
				}()
			}
		case <-time.After(time.Second * global.Conf.RunParams.GetOrderInterval):
			GetSignOrder()
		}
	}
}

// batch exec evm order
func BatchExecHyOrder() {
	accountChannel = make(ChannelI[int], len(robotAccountList))
	for accountIndex, _ := range robotAccountList {
		accountChannel <- accountIndex
		global.Log.Info("====> accountChannel push %v_%v", accountIndex, robotAccountList[accountIndex].Addr)
	}
	for {
		select {
		case payOrderID := <-hYOrderChannel:
			global.Log.Info("====> hYOrderChannel pop payOrdeID[%v]", payOrderID)
			if accountIndex, ok := <-accountChannel; ok {
				global.Log.Info("====> accountChannel pop index_[%v]_address[%v]", accountIndex, robotAccountList[accountIndex].Addr)
				go func() {
					defer func() {
						if err := recover(); err != nil {
							global.Log.Error("====>  hYOrderChannel exec payOrderID_[%v] fail：%v", payOrderID, err)
						}
						accountChannel <- accountIndex
						global.Log.Info("====> accountChannel push index_[%v]_address[%v]", accountIndex, robotAccountList[accountIndex].Addr)
					}()
					// exec hY Order
					ExecHyOrder(payOrderID, robotAccountList[accountIndex])
				}()
			}
		case <-time.After(time.Second * global.Conf.RunParams.GetOrderInterval):
			GetExecOrder()
		}
	}
}

func MakeBotSign(signAccount *model.Account, hYOrder *model.HybridgeOrder) {
	global.Log.Info("====> orderid [%v] begin handle HyOrder %v", hYOrder.ID, hYOrder)
	var hyOrderInfo HyOrderInfo
	hyOrderInfo.execAccount = signAccount
	if hyOrderInfo.NewOrderInfo(hYOrder) {
		if hYOrder.Type != "2" && !hyOrderInfo.CheckChainSrcOrder() {
			if hyOrderInfo.DelayGetMinute > 0 && time.Since(hyOrderInfo.InsertTime).Minutes() < hyOrderInfo.DelayGetMinute {
				global.Log.Error("====>  orderID_[%v] CheckChainSrcOrder not find srcorder try again ", hyOrderInfo.ID)
				hyOrderInfo.UpdateOrderStatus(hYOrder.PayorderID, model.WaitBegin, "")
				key := model.RDPrefixEvmAPIOrder + hyOrderInfo.EParam.PayTaskId.String()
				detRedisByKey(key)
				return
			}
			hyOrderInfo.UpdateOrderStatus(hYOrder.PayorderID, model.Failed, "OrderTimeOut")
			return
		}
		// begin make sign
		if !hyOrderInfo.MakeSign(signAccount) {
			global.Log.Error("====> orderid [%v] MakeSign fail", hyOrderInfo.ID)
			hyOrderInfo.UpdateOrderStatus(hYOrder.PayorderID, model.Failed, "MakeSignFail")
		} else {
			hyOrderInfo.UpdateOrderStatus(hYOrder.PayorderID, model.WaitExec, "WaitExec")
		}
	} else {
		hyOrderInfo.UpdateOrderStatus(hYOrder.PayorderID, model.Failed, "NewHyOrderInfoFail")
		global.Log.Error("====> orderid [%v] NewHyOrderInfo fail", hyOrderInfo.ID)
	}
}

func ExecHyOrder(payorderID string, execAccount *model.Account) {
	global.Log.Info("====> orderid [%v] begin handle payorderID %v", payorderID)
	var hyOrderInfo HyOrderInfo
	if hyOrderInfo.NewOrderInfoByOrderID(payorderID) {
		var trHash string
		var ok bool
		if hyOrderInfo.DstChainNetWorkInfo.ChainID == model.CosmosChainID {
			trHash, ok = hermitClient.ExecuteOrder(&hyOrderInfo)
		} else {
			trHash, ok = hyOrderInfo.ExecuteOrder(execAccount)
		}
		if ok {
			hyOrderInfo.UpdateOrderStatus(payorderID, model.Successed, trHash)
		} else {
			if hyOrderInfo.DstChainNetWorkInfo.ChainID != model.CosmosChainID {
				hyOrderInfo.UpdateOrderStatus(payorderID, model.Failed, trHash)
			}
		}
	} else {
		hyOrderInfo.UpdateOrderStatus(payorderID, model.Failed, "NewOrderInfoByOrderIDFail")
		global.Log.Error("====> orderid [%v] NewOrderInfoByOrderID fail", payorderID)
	}
}

func getHandleHyOrderList() bool {
	globalHyOrderList = []model.HybridgeOrder{}
	apiUrl := global.Conf.RunParams.TaskApi + "api/crosschain/getcrosschaintask"
	method := "getcrosschaintask"
	timeStamp := strconv.Itoa(int(time.Now().Unix()))
	sign, ok := MakeAPISign(method+" timestamp:"+timeStamp, robotAccountList[0])
	if !ok {
		global.Log.Error("getHandleHyOrderList makeAPISign error")
		return false
	}
	data := make(url.Values)
	data.Add("bot", strings.ToLower(robotAccountList[0].Addr))
	data.Add("timeStamp", timeStamp)
	data.Add("sign", sign)
	resp, err := http.PostForm(apiUrl, data)
	if err != nil {
		global.Log.Error("getHandleHyOrderList makeAPISign error")
		return false
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		global.Log.Error("getHandleHyOrderList makeAPISign error")
		return false
	}
	global.Log.Info("====> getHandleHyOrderList resposeJson [%s]", body)
	if err != nil {
		global.Log.Error("getHandleHyOrderList error：%v", err.Error())
		return false
	}
	var apiResult model.ApiResult
	if err := json.Unmarshal([]byte(body), &apiResult); err != nil {
		global.Log.Info("getHandleHyOrderList error：%v", err.Error())
	}
	if apiResult.Data.ID > 0 {
		globalHyOrderList = append(globalHyOrderList, apiResult.Data)
	} else {
		global.Log.Info("getHandleHyOrderList msg：%v", apiResult.Msg)
	}

	return true
}

func getRedisHyOrderTask() (ok bool) {
	var err error
	hyOrderKeys, err = global.RD.Keys("*" + model.RDPrefixEvmOrder + "*")
	if err != nil {
		global.Log.Error("====>  redis GetString hyOrderInfoJson error： %s", err.Error())
		return
	}
	return true
}

func detRedisByKey(payOrderID string) {
	hyOrderInfoJson, err := global.RD.GetString(payOrderID)
	if err != nil {
		global.Log.Error("====>  redis GetString hyOrderInfoJson error： %s", err.Error())
		// return
	}
	if hyOrderInfoJson != "" {
		_, err := global.RD.DelKey(payOrderID)
		if err != nil {
			global.Log.Error("====>  del redis key_[%v] error ", payOrderID)
		}
	}
}

// make api sign to get order task
func MakeAPISign(signStr string, signAccount *model.Account) (string, bool) {
	var prikey, _ = crypto.HexToECDSA(signAccount.PriKey)
	signBytes := []byte(signStr)
	global.Log.Info("====>  MakeAPISign encodePacked: [%v]", hex.EncodeToString(ether.EncodePacked(signBytes[:])))
	hash := crypto.Keccak256Hash(ether.EncodePacked(signBytes[:]))
	{
		// encode test
		// TypeString, _ := abi.NewType("string", "", nil)
		// arguments := abi.Arguments{
		// 	{
		// 		Type: TypeString,
		// 	},
		// }
		// packed, _ := arguments.Pack(signBytes)
		// global.Log.Info("====> packed :%v", packed)
		// hash00 := crypto.Keccak256Hash(packed)
		// global.Log.Info("====> digest :%v ", hash00)
		// hash := crypto.Keccak256Hash()
		// hash, _ := hexutil.Decode("0x959D110963E9495CA2F35CAAEA631735219F3EF00672B72ECE6B5A3046AC800C")
		// crysign, err := crypto.Sign(hash[:32], prikey)
	}
	crysign, err := crypto.Sign(hash[:32], prikey)
	if err != nil {
		return "", false
	}
	crysign[64] += 27
	crysigns := crysign
	signRStr := hexutil.Encode(crysigns)
	global.Log.Info("====> digest :%v signature :%v", hash, signRStr)
	Signature := common.FromHex(signRStr)
	return hex.EncodeToString(Signature), true
}
